// localSystemInfo.js
const os = require('os');
const { execSync } = require('child_process');

class LocalSystemInfo {
    bytesToHuman(bytes) {
        if (bytes < 1024) return bytes + ' B';
        const units = ['KB', 'MB', 'GB', 'TB'];
        let i = -1;
        do {
            bytes /= 1024;
            i++;
        } while (bytes >= 1024);
        return bytes.toFixed(1) + ' ' + units[i];
    }

    getKernelVersion() {
        const platform = os.platform();
        let version = os.release();
        const architecture = os.arch();

        if (platform === 'linux' || platform === 'darwin') {
            try {
                const unameOutput = execSync('uname -a').toString();
                version = unameOutput.split(' ')[2];
            } catch (error) {
                console.error('Error getting kernel version:', error);
            }
        }

        return { kernelVersion: version, architecture };
    }

    getOSInfo() {
        const platform = os.platform();

        if (platform === 'linux') {
            try {
                const releaseInfo = execSync('cat /etc/os-release').toString();
                const lines = releaseInfo.split('\n');
                let prettyName = null;
                let name = null;

                lines.forEach((line) => {
                    if (line.startsWith('PRETTY_NAME=')) {
                        const [, value] = line.split('=');
                        prettyName = value.replace(/"/g, '').trim();
                    } else if (line.startsWith('NAME=')) {
                        const [, value] = line.split('=');
                        name = value.replace(/"/g, '').trim();
                    }
                });

                return prettyName || name || 'Linux';
            } catch (error) {
                console.error('Error getting OS info:', error);
                return 'Linux';
            }
        } else if (platform === 'win32') {
            try {
                const versionInfo = execSync('wmic os get Caption').toString();
                const captionLine = versionInfo.split('\n')[1].trim();
                return captionLine || 'Windows';
            } catch (error) {
                console.error('Error getting OS info:', error);
                return 'Windows';
            }
        } else if (platform === 'darwin') {
            try {
                const versionInfo = execSync('sw_vers -productName && sw_vers -productVersion').toString();
                const [productName, productVersion] = versionInfo.split('\n');
                return `${productName.trim()} ${productVersion.trim()}`;
            } catch (error) {
                console.error('Error getting OS info:', error);
                return 'macOS';
            }
        } else {
            return 'Unknown OS';
        }
    }

    getMemoryInfo() {
        const platform = os.platform();
        let memoryInfo = {
            total: os.totalmem(),
            free: os.freemem()
        };

        try {
            if (platform === 'linux') {
                const output = execSync('free -b').toString();
                const lines = output.split('\n');
                const memInfo = lines[1].match(/\d+/g).map(Number);

                memoryInfo = {
                    total: memInfo[0],
                    used: memInfo[1],
                    free: memInfo[2],
                    shared: memInfo[3],
                    cache: memInfo[4],
                    available: memInfo[5]
                };
            } else if (platform === 'darwin') {
                const output = execSync('vm_stat').toString();
                const memStats = {};
                output.split('\n').forEach((line) => {
                    const parts = line.trim().split(':');
                    if (parts.length === 2) {
                        memStats[parts[0]] = parseInt(parts[1]);
                    }
                });

                const pageSize = 16384; // macOS default page size
                const freeMemory = (memStats['Pages free'] + memStats['Pages inactive'] + memStats['Pages speculative']) * pageSize;
                const usedMemory = (memStats['Pages active'] + memStats['Pages wired down']) * pageSize;
                const cacheMemory = memStats['Pages purgeable'] * pageSize;

                memoryInfo = {
                    total: os.totalmem(),
                    free: freeMemory,
                    used: usedMemory,
                    cache: cacheMemory,
                    available: freeMemory + cacheMemory
                };
            } else if (platform === 'win32') {
                const output = execSync('wmic OS get FreePhysicalMemory,TotalVisibleMemorySize /Value').toString();
                const lines = output.trim().split('\n');
                const values = Object.fromEntries(lines.map((line) => line.split('=')));
                memoryInfo = {
                    total: Number(values['TotalVisibleMemorySize']) * 1024,
                    free: Number(values['FreePhysicalMemory']) * 1024,
                    used: Number(values['TotalVisibleMemorySize'] - values['FreePhysicalMemory']) * 1024
                };
            }
        } catch (error) {
            console.error('Error fetching memory info:', error);
        }

        // Convert bytes to human-readable format
        return {
            total: this.bytesToHuman(memoryInfo.total),
            used: this.bytesToHuman(memoryInfo.used),
            free: this.bytesToHuman(memoryInfo.free),
            shared: this.bytesToHuman(memoryInfo.shared || 0),
            cache: this.bytesToHuman(memoryInfo.cache || 0),
            available: this.bytesToHuman(memoryInfo.available || 0)
        };
    }

    getCPUInfo() {
        const cpus = os.cpus();
        return {
            cpuModel: cpus[0].model,
            cpuCores: cpus.length
        };
    }

    getGPUInfo() {
        let gpuInfo = [];

        try {
            const platform = os.platform();
            if (platform === 'linux') {
                const output = execSync('lspci | grep VGA').toString();
                gpuInfo = output
                    .split('\n')
                    .map((line) => {
                        const name = line.split(': ')[1];
                        return { name };
                    })
                    .filter((gpu) => gpu.name);
            } else if (platform === 'win32') {
                const output = execSync('wmic path win32_videocontroller get name').toString();
                gpuInfo = output
                    .split('\n')
                    .slice(1)
                    .map((name) => ({ name: name.trim() }))
                    .filter((gpu) => gpu.name);
            } else if (platform === 'darwin') {
                const output = execSync('system_profiler SPDisplaysDataType | grep Chipset').toString();
                gpuInfo = output
                    .split('\n')
                    .map((line) => {
                        const name = line.split(': ')[1];
                        return { name };
                    })
                    .filter((gpu) => gpu.name);
            }
        } catch (error) {
            console.error('Error getting GPU info:', error);
        }

        return gpuInfo;
    }

    getCUDAInfo() {
        try {
            // Get CUDA runtime version from nvidia-smi
            const smiResult = execSync('nvidia-smi').toString();
            const cudaVersionMatch = smiResult.match(/CUDA Version: (\d+\.\d+)/);
            const nvidiaSmiCUDA = cudaVersionMatch ? cudaVersionMatch[1] : 'Unknown';

            // Get CUDA version using nvcc (if available)
            const nvccResult = execSync('nvcc --version').toString();
            const nvccVersionMatch = nvccResult.match(/release (\d+\.\d+)/);
            const nvccVersion = nvccVersionMatch ? nvccVersionMatch[1] : 'Unknown';

            return { nvidiaSmiCUDA, nvccVersion };
        } catch (error) {
            // console.error(error);
            return { nvidiaSmiCUDA: 'null', nvccVersion: 'null' };
        }
    }

    getCuDNNInfo() {
        try {
            // Check common cuDNN header for version info
            const result = execSync('grep -A 2 CUDNN_MAJOR -h /usr/include/cudnn*.h').toString();
            const majorMatch = result.match(/#define CUDNN_MAJOR (\d+)/);
            const minorMatch = result.match(/#define CUDNN_MINOR (\d+)/);
            const patchMatch = result.match(/#define CUDNN_PATCHLEVEL (\d+)/);

            const major = majorMatch ? majorMatch[1] : 'Unknown';
            const minor = minorMatch ? minorMatch[1] : 'Unknown';
            const patch = patchMatch ? patchMatch[1] : 'Unknown';

            const version = `${major}.${minor}.${patch}`;
            return { version };
        } catch (error) {
            // console.error(error);
            return { version: 'null' };
        }
    }

    filterAndSummarizeDisks(disks) {
        const filteredDisks = disks.filter((disk) => {
            return disk.mount === '/' || disk.mount.startsWith('/home');
        });

        const summary = filteredDisks.reduce(
            (acc, disk) => {
                acc.totalUsed += parseInt(disk.used);
                acc.totalAvailable += parseInt(disk.available);
                if (disk.size) {
                    acc.totalSize += parseInt(disk.size);
                } else {
                    acc.totalSize += acc.totalUsed + acc.totalAvailable;
                }
                return acc;
            },
            { totalSize: 0, totalUsed: 0, totalAvailable: 0 }
        );

        return {
            totalSize: this.bytesToHuman(summary.totalSize),
            totalUsed: this.bytesToHuman(summary.totalUsed),
            totalAvailable: this.bytesToHuman(summary.totalAvailable),
            percent: `${((summary.totalUsed / summary.totalSize) * 100).toFixed(2)}%`
        };
    }

    getDiskInfo() {
        const platform = os.platform();
        let disks = [];

        try {
            if (platform === 'linux') {
                const output = execSync('df -B1 --output=size,used,avail,target').toString();
                const lines = output.trim().split('\n');
                disks = lines.slice(1).map((line) => {
                    const [size, used, available, mount] = line.split(/\s+/);
                    return {
                        size: parseInt(size),
                        used: parseInt(used),
                        available: parseInt(available),
                        mount
                    };
                });
            } else if (platform == 'darwin') {
                const output = execSync('df -b').toString();
                const lines = output.trim().split('\n');
                disks = lines.slice(1).map((line) => {
                    const [filesystem, blocks, used, available, percent, mount] = line.split(/\s+/);
                    return {
                        used: parseInt(used) * 512,
                        available: parseInt(available) * 512,
                        percent,
                        mount
                    };
                });
            } else if (platform === 'win32') {
                const output = execSync('wmic logicaldisk get size,freespace,caption').toString();
                const lines = output.trim().split('\n').slice(1);
                disks = lines.map((line) => {
                    const [caption, freeSpace, size] = line.trim().split(/\s+/);
                    return {
                        drive: caption,
                        size: parseInt(size),
                        free: parseInt(freeSpace),
                        used: parseInt(size) - parseInt(freeSpace)
                    };
                });
            }
        } catch (error) {
            console.error('Error fetching disk info:', error);
        }

        return this.filterAndSummarizeDisks(disks);
    }

    getEnvList() {
        const command = `conda env list`;
        try {
            const envListResult = execSync(command).toString();
            const envList = this.parseEnvList(envListResult);
            return envList;
        } catch (error) {
            console.error('Error getting Conda environments:', error.message);
            return [];
        }
    }

    parseEnvList(output) {
        const lines = output.trim().split('\n');
        const environments = [];

        lines.forEach((line) => {
            if (line && !line.startsWith('#')) {
                const parts = line.trim().split(/\s+/);
                if (parts.length >= 2) {
                    let envName, envPath;

                    if (parts[1] === '*') {
                        envName = parts[0];
                        envPath = parts[2];
                    } else {
                        envName = parts[0];
                        envPath = parts[1];
                    }

                    environments.push({
                        name: envName,
                        path: envPath
                    });
                }
            }
        });

        return environments;
    }

    getSystemInfo() {
        const { kernelVersion, architecture } = this.getKernelVersion();
        const osInfo = this.getOSInfo();
        const memoryInfo = this.getMemoryInfo();
        const { cpuModel, cpuCores } = this.getCPUInfo();
        const gpuInfo = this.getGPUInfo();
        const cudaInfo = this.getCUDAInfo();
        const cuDnnInfo = this.getCuDNNInfo();
        const diskInfo = this.getDiskInfo();
        const condaEnvs = this.getEnvList();

        return {
            kernelVersion,
            architecture,
            osInfo,
            memoryInfo,
            cpuInfo: { cpuModel, cpuCores },
            gpuInfo,
            cudaInfo,
            cuDnnInfo,
            diskInfo,
            condaEnvs
        };
    }
}

module.exports = { LocalSystemInfo };
